/*
 *  linux/kernel/system_call.s
 *
 *  (C) 1991  Linus Torvalds
 */
# system_call.s 文件包含系统调用底层处理子程序。由于有些代码比较类似，所以同时也包括时钟中断处理句柄。硬盘和软盘的中断处理程序也在这里。
/*  注意 这段代码处理信号识别，在每次时钟中断和系统调用之后都会进行识别，一般中断信号并不处理信号识别，因为会给系统造成混乱
 *  system_call.s  contains the system-call low-level handling routines.
 * This also contains the timer-interrupt handler, as some of the code is
 * the same. The hd- and flopppy-interrupts are also here.
 *
 * NOTE: This code handles signal-recognition, which happens every time
 * after a timer-interrupt and after each system call. Ordinary interrupts
 * don't handle signal-recognition, as that would clutter them up totally
 * unnecessarily.
 *
 * Stack layout in 'ret_from_system_call':
 *
 *	 0(%esp) - %eax
 *	 4(%esp) - %ebx
 *	 8(%esp) - %ecx
 *	 C(%esp) - %edx
 *	10(%esp) - %fs
 *	14(%esp) - %es
 *	18(%esp) - %ds
 *	1C(%esp) - %eip
 *	20(%esp) - %cs
 *	24(%esp) - %eflags
 *	28(%esp) - %oldesp
 *	2C(%esp) - %oldss
 */

SIG_CHLD	= 17            # 定义 SIG_CHLD 信号（子进程停止或结束）

EAX		= 0x00              # 堆栈中各个寄存器的偏移位置
EBX		= 0x04
ECX		= 0x08
EDX		= 0x0C
FS		= 0x10
ES		= 0x14
DS		= 0x18
EIP		= 0x1C
CS		= 0x20
EFLAGS		= 0x24
OLDESP		= 0x28          # 当有特权级变化时
OLDSS		= 0x2C
// 以下这些时任务结构（task_struct）中变量的偏移值
state	= 0		# these are offsets into the task-struct.   # 进程状态码
counter	= 4     # 任务运行时间计数，运行时间片
priority = 8    # 运行优先数，任务开始运行时counter=priority 越大则运行时间越长
signal	= 12    # 信号位图 每个比特位代表一种信号，信号值=位偏移值+1
sigaction = 16		# MUST be 16 (=len of sigaction) # sigaction 结构长度必须时16字节 信号执行属性结构数组的偏移值，对应信号将要执行的操作和标志信息
blocked = (33*16)   # 受阻塞信号位图的偏移量
# 以下定义在 sigaction结构中的偏移量
# offsets within sigaction
sa_handler = 0          # 信号处理过程的句柄（描述符）
sa_mask = 4             # 信号量屏蔽码
sa_flags = 8            # 信号集
sa_restorer = 12        # 恢复函数指针

nr_system_calls = 72    # Linux 0.11 版本内核中的系统调用总数

/*
 * Ok, I get parallel printer interrupts while using the floppy for some
 * strange reason. Urgel. Now I just ignore them.
 */ /* 在使用软驱时收到了并行打印机中断，但现在可以不管他 */
.globl _system_call,_sys_fork,_timer_interrupt,_sys_execve
.globl _hd_interrupt,_floppy_interrupt,_parallel_interrupt
.globl _device_not_available, _coprocessor_error

.align 2                            # 内存4字节对齐
bad_sys_call:                       # 错误的系统调用号从这里返回
	movl $-1,%eax                   # eax 中置-1，退出中断。
	iret
.align 2
reschedule:                         # 重新执行调度程序入口 调度程序 schedule（）
	pushl $ret_from_sys_call        # 将 ret_from_sys_call 的地址入栈
	jmp _schedule
.align 2
_system_call:                       # int 0x80 --Linux系统调用入口点（调用中断int 0x80，eax中是调用号）
	cmpl $nr_system_calls-1,%eax    # 调用号如果超出范围的话就在eax中置-1并退出
	ja bad_sys_call
	push %ds                        # 保存原段寄存器值
	push %es
	push %fs
	pushl %edx                      # ebx ecx edx 中放着系统调用相应的C语言函数的调用参数
	pushl %ecx		# push %ebx,%ecx,%edx as parameters
	pushl %ebx		# to the system call
	movl $0x10,%edx		# set up ds,es to kernel space
	mov %dx,%ds                     # ds es指向内核数据段（全局描述符表中的数据段描述符）
	mov %dx,%es
	movl $0x17,%edx		# fs points to local data space
	mov %dx,%fs                     # fs指向局部数据段（局部描述符表中的数据段描述符）
	call _sys_call_table(,%eax,4)   # 这边的含义为：调用地址=_sys_call_table+%eax*4。见文件后的说明。对应的C程序中的sys_call_table在include/linux/sys.h中，其中定义了一个包括72个系统调用C处理函数地址数组表
	pushl %eax                      # 把系统调用返回值入栈
	movl _current,%eax              # 取当前任务（进程）数据结构地址 -> eax
	cmpl $0,state(%eax)		# state # 97-100行查看当前任务的运行状态。如果不在就绪状态就去调用调度程序 如果该任务在就绪状态但时间片已经用完，也执行调度程序
	jne reschedule
	cmpl $0,counter(%eax)		# counter
	je reschedule
ret_from_sys_call:  # 以下这段代码执行从系统调用C函数返回后，对信号量进行识别处理。首先判别当前任务是否是初始任务Task0 如果是则不必对其进行信号量方面的处理，直接返回。
	movl _current,%eax		# task[0] cannot have signals   # 103行上的task对应的C程序中的task[]数组，直接引用task相当于引用task[0]
	cmpl _task,%eax
	je 3f                   # 向前（forward）跳转到标号3
	cmpw $0x0f,CS(%esp)		# was old code segment supervisor ? # 通过对原调用程序代码选择符的检查来判断调用程序是否是内核中的任务（task0） 如果是就直接退出中断，否则需进行信号量的处理。这里比较选择符是否为普通用户代码段的选择符 0x000f（RPL=3，局部表，第一个段（代码段）） 如果不是则跳转退出中断程序
	jne 3f
	cmpw $0x17,OLDSS(%esp)		# was stack segment = 0x17 ?
	jne 3f                      # 下面这段代码（109-120）的用途是首先取当前任务结构中的信号位图（32位，每位代表1个信号）然后用任务结构中的信号阻塞（屏蔽）码，阻塞不允许的信号位，取得数值最小的信号值，再把原信号位图中该信号对应的位复位（置0）最后将信号值作为参数之一调用do_singnal（）。do_singnal（）在signal.c中 其参数包括13个入栈的信息
	movl signal(%eax),%ebx      # 取信号位图-ebx 每一位代表一种信号，共32个信号
	movl blocked(%eax),%ecx     # 取阻塞（屏蔽）信号位图->ecx
	notl %ecx                   # 每位取反
	andl %ebx,%ecx              # 获取许可的信号位图
	bsfl %ecx,%ecx              # 从低位（位0）开始扫描位图，看是否有1的位 若有，则ecx保留该位的偏移值
	je 3f                       # 如果没有信号则向前跳转退出
	btrl %ecx,%ebx              # 复位该信号 ebx 含有原signal的位图
	movl %ebx,signal(%eax)      # 重新保存signal位图信息->current-》signal
	incl %ecx                   # 将信号调整为从1开始的数（1-32）
	pushl %ecx                  # 信号值入栈作为调用do_signal的参数之一
	call _do_signal             # 调用C函数信号处理程序
	popl %eax                   # 弹出信号值
3:	popl %eax
	popl %ebx
	popl %ecx
	popl %edx
	pop %fs
	pop %es
	pop %ds
	iret
# int16 --下面这段代码处理协处理器发出的出错信号。跳转执行C函数math_error 返回后将跳转到 ret_from_sys_call 处继续执行
.align 2
_coprocessor_error:
	push %ds
	push %es
	push %fs
	pushl %edx
	pushl %ecx
	pushl %ebx
	pushl %eax
	movl $0x10,%eax             # ds,es置为指向内核数据段
	mov %ax,%ds
	mov %ax,%es
	movl $0x17,%eax             # fs 置为指向局部数据段（出错程序的数据段）
	mov %ax,%fs
	pushl $ret_from_sys_call    # 把下面调用返回的地址入栈
	jmp _math_error             # 执行C函数 math_error
# int7 -- 设备不存在或协处理器不存在。 若控制器寄存器CR0的EM标志置位，则当CPU执行一个转义指令时就汇引发该中断，这样就可以有机会让这个中断处理程序模拟转义指令。
.align 2                        # CRO 的TS标志时在CPU执行任务转换时设置的。TS可以用来确定什么时候协处理器中的内容与CPU正在执行的任务不匹配了。
_device_not_available:          # 当CPU在运行一个转义指令时发现TS置位了，就会引发该中断。此时就应该恢复新任务的协处理器执行状态。
	push %ds                    # 参见sched.c中的说明。该中最后将转移到标号ret_from_sys_call处执行
	push %es
	push %fs
	pushl %edx
	pushl %ecx
	pushl %ebx
	pushl %eax
	movl $0x10,%eax
	mov %ax,%ds
	mov %ax,%es
	movl $0x17,%eax             # fs置为指向局部数据段（出错程序的数据段）
	mov %ax,%fs
	pushl $ret_from_sys_call    # 把下面跳转或调用的返回地址入栈
	clts				# clear TS so that we can use math
	movl %cr0,%eax
	testl $0x4,%eax			# EM (math emulation bit)
	je _math_state_restore      # 如果不是EM引起的中断，则恢复新任务协处理状态
	pushl %ebp                  # 执行C函数 math_state_restore
	pushl %esi
	pushl %edi
	call _math_emulate          # 调用C函数 math_emulate
	popl %edi
	popl %esi
	popl %ebp
	ret                         # 这边的ret将跳转到ret_from_sys_call处执行
# int32 -- （int 0x20）时钟中断处理程序。中断频率被设置为100HZ 定时芯片8253/8254是在sched.c处初始化的。英雌这里jiffies每10ms加1。
.align 2    # 这段代码将jiffies增1，发送结束中断指令给8259控制器，然后用当前特权级作为参数调用C函数do_timer。当调用返回时转去检测并处理信号。
_timer_interrupt:
	push %ds		# save ds,es and put kernel data space
	push %es		# into them. %fs is used by _system_call
	push %fs
	pushl %edx		# we save %eax,%ecx,%edx as gcc doesn't
	pushl %ecx		# save those across function calls. %ebx
	pushl %ebx		# is saved as we use that in ret_sys_call
	pushl %eax
	movl $0x10,%eax             # ds es置为指向内核数据段
	mov %ax,%ds
	mov %ax,%es
	movl $0x17,%eax             # fs 置为指向局部数据段（出错程序的数据段）
	mov %ax,%fs
	incl _jiffies               # 由于初始化中断控制芯片时没有采用自动EOI，所以这里需要发指令结束该硬件中断。
	movb $0x20,%al		# EOI to interrupt controller #1
	outb %al,$0x20              # 才做命令字OCW2送0x20端口
	movl CS(%esp),%eax          # 这三局从选择符中去除当前特权级别（0 or 3）并压入堆栈，作为do_timer的参数
	andl $3,%eax		# %eax is CPL (0 or 3, 0=supervisor)
	pushl %eax
	call _do_timer		# 'do_timer(long CPL)' does everything from
	addl $4,%esp		# task switching to accounting ...
	jmp ret_from_sys_call
# 这是 sys_execve（）系统调用。取中断调用程序的代码指针作为参数调用C函数 do_execve（）（fs/exec.c）
.align 2
_sys_execve:
	lea EIP(%esp),%eax
	pushl %eax
	call _do_execve
	addl $4,%esp                # 丢弃调用时压入栈的EIP值
	ret
# sys_fork（）调用，用于创建子进程，是system_call功能2。原形在include/linux/sys.h中。首先调用C函数find_empty_process()取的一个进程号
.align 2    #若返回附属则说明目前任务数组已满，然后调用 copy_process（）复制进程。
_sys_fork:
	call _find_empty_process    # 调用 find_empty_process（）
	testl %eax,%eax
	js 1f
	push %gs
	pushl %esi
	pushl %edi
	pushl %ebp
	pushl %eax
	call _cess                  # 调用C函数copy_process
	addl $20,%esp               # 丢弃这里的所有压栈内容
1:	ret
# int46 -- 硬盘中断处理程序，响应硬件中断请求IRQ14。当硬盘操作完成或出错就会发出中断信号。首先向8259A中断控制从芯片发送结束硬件中断指令（EOI）
_hd_interrupt:  # 然后取变量do_hd中的函数指针放入edx寄存器中，并置do_hd为NULL，接着哦按段edx函数指针是否为空。如果为空，则给edx赋值 unexpected_hd_interrupt（）
	pushl %eax  # 用于显示出错信息。随后向8259A主芯片送EOI指令，并调用edx中指针指向的函数：read_intr（）write_intr（）或unexpected_hd_interrupt（）
	pushl %ecx
	pushl %edx
	push %ds
	push %es
	push %fs
	movl $0x10,%eax             # ds，es置为内核数据段
	mov %ax,%ds
	mov %ax,%es
	movl $0x17,%eax             # fs置为调用程序的局部数据段
	mov %ax,%fs
	movb $0x20,%al              # 由于初始化中断控制芯片时没有采用自动EOI 所以这里需要发指令结束该硬件中断
	outb %al,$0xA0		# EOI to interrupt controller #1    # 送从8259A
	jmp 1f			# give port chance to breathe
1:	jmp 1f                      # 延时作用
1:	xorl %edx,%edx
	xchgl _do_hd,%edx           # do_hd 是函数指针，将被赋值read_intr或write_intr函数地址，放到edx后就将do_hd置为NULL
	testl %edx,%edx             # 测试函数指针是否为NULL
	jne 1f                      # 若空，则使指针指向C函数或unexpected_hd_interrupt（）
	movl $_unexpected_hd_interrupt,%edx
1:	outb %al,$0x20              # 送主8259A中断控制器EOI指令（结束硬件中断）
	call *%edx		# "interesting" way of handling intr.
	pop %fs                     # 上句调用 do_hd 指向的C函数
	pop %es
	pop %ds
	popl %edx
	popl %ecx
	popl %eax
	iret
# int38 -- 软盘驱动器中断处理程序 响应硬件中断请求IRQ6。
_floppy_interrupt:  # 其处理过程与上面对硬盘的处理基本一样。首先向8259A中断控制器主芯片发送EOI指令，然后取变量do_floppy中的函数指针放入eax寄存器中
	pushl %eax      # 并置 do_floppy 为NULL， 接着判断eax函数指针是否为空。如为空，则给eax赋值指向unexpected_floppy_interrupt
	pushl %ecx      # 用于显示出错信息。随后调用eax指向的函数
	pushl %edx
	push %ds
	push %es
	push %fs
	movl $0x10,%eax             # ds，es置为内核数据段
	mov %ax,%ds
	mov %ax,%es
	movl $0x17,%eax             # fs置为调用程序的局部数据段
	mov %ax,%fs
	movb $0x20,%al              # 送主8259A中断控制器EOI指令
	outb %al,$0x20		# EOI to interrupt controller #1
	xorl %eax,%eax              # 下句do_floppy为一函数指针，将被赋值实际处理C函数程序
	xchgl _do_floppy,%eax       # 放到eax急促请你后就将do_floppy指针变量置空
	testl %eax,%eax             # 测试函数指针是否=NULL
	jne 1f                      # 若空，则使指针指向C函数unexpected_floppy_interrupt
	movl $_unexpected_floppy_interrupt,%eax
1:	call *%eax		# "interesting" way of handling intr.
	pop %fs                     # 上句调用 do_floppy指向的函数
	pop %es
	pop %ds
	popl %edx
	popl %ecx
	popl %eax
	iret
# int 39 并行口中断处理程序，对应硬件中断请求IRQ7
_parallel_interrupt:            # 本版本内核还未实现 这里指示发送EOI指令
	pushl %eax
	movb $0x20,%al
	outb %al,$0x20
	popl %eax
	iret
